Add data attributes to option tags in django admin Select field
You can also be interested in:
Sometimes can be usefull to add some data-attribute to the option tags of a select field in your django admin forms. For example you can then use this attributes to perform some js manipulation, something like: hide all the stuff with data-foo equal to 'bar'. For example, I have a select field named 'providers', and then an invoice field, each invoice is tied to a provider. I want to implement a cascasde select, so that when the user selects a provider, then he can choose only the related invoices. This can be achieved with data-attributes and a few lines of js.
Ok, so how can we add this data attributes? We need 3 things:
- create a custom widget which inherits from Select and adds the logic to set the data attributes;
- subclass the ModelForm class in order to instantiate the new widget;
- associate the new form class to the ModelAdmin class.
The custom widget
It's quite easy: we just need to add some attributes to the options tags. After looking deeply at the django source code, I understood that this can be done simply by overriding the create_option
method of the ChoiceWidget
class. There is no need to override templates or other things, here comes the code:
from django.forms.widgets import Select class DataAttributesSelect(Select): def __init__(self, attrs=None, choices=(), data={}): super(DataAttributesSelect, self).__init__(attrs, choices) self.data = data def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): # noqa option = super(DataAttributesSelect, self).create_option(name, value, label, selected, index, subindex=None, attrs=None) # noqa # adds the data-attributes to the attrs context var for data_attr, values in self.data.iteritems(): option['attrs'][data_attr] = values[option['value']] return option
You can place it in a widgets.py
file inside your app dir.
N.B. line 13 for python 3 becomes as follows:
for data_attr, values in self.data.items():
Subclass the ModelForm
This code has been improved after comments suggestions, see the improved version below.
Create a forms.py
file inside you app dir:
from django import forms from .widgets import DataAttributesSelect from .models import MyChoiceModel class MyModelAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(MyModelAdminForm, self).__init__(*args, **kwargs) # preparation of data attributes. It is a dictionary where # the key is the attribute name and the value another dict. # In the second dict, for each key which corresponds to the option # value, the attribute value is given data = {'data-foo': {'': ''}} # empty option for f in MyChoiceModel.objects.all(): data['data-foo'][f.id] = f.foo.id self.fields['myselectfield'].widget = DataAttributesSelect( choices=[('', '---------')] + [(f.id, str(f)) for f in MyChoiceModel.objects.all()], # noqa data=data )
Use the new created form inside the ModelAdmin class
Super easy:
from django.contrib import admin from .forms import MyModelAdminForm class MyModeAdmin(admin.ModelAdmin): list_display = ('bar', ) # ... form = MyModelAdminForm admin.site.register(MyModel, MyModeAdmin)
That's it!
Update 17 october 2017
As Venelin Stoykov's pointed out, the form class code can be improved, avoiding to hit the database too many times, so here comes the rewritten code:
from django import forms from .widgets import DataAttributesSelect from .models import MyChoiceModel class MyModelAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(MyModelAdminForm, self).__init__(*args, **kwargs) data = {'data-foo': dict(MyModelChoice.objects.values_list('id', 'foo'))} data['data-foo'][''] = '' # empty option self.fields['myselectfield'].widget = DataAttributesSelect( choices=self.fields['myselectfield'].choices, data=data )
Update 13 november 2017
The proposed method had an unpleasant drawback. The add related features in the admin site got lost, so there was no way to add or edit related models contextually.
I surfed the django admin code in order to find a way to preserve this functionality, and it turned out it was quite simple. The django way of add such functionality is a wrapper function defined in django/contrib/admin/widgets.py
.
Django calls this wrapper around the fk and m2m fields - which are class attributes - before actually instantiating the form class, that's why in the init method, we can just copy some of the needed wrapper arguments:
from django import forms from django.contrib.admin.widgets import RelatedFieldWidgetWrapper from .widgets import DataAttributesSelect from .models import MyChoiceModel class MyModelAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(MyModelAdminForm, self).__init__(*args, **kwargs) data = {'data-foo': dict(MyModelChoice.objects.values_list('id', 'foo'))} data['data-foo'][''] = '' # empty option self.fields['myselectfield'].widget = RelatedFieldWidgetWrapper( DataAttributesSelect( choices=self.fields['myselectfield'].choices, data=data ), self.fields['myselectfield'].widget.rel, self.fields['myselectfield'].widget.admin_site, self.fields['myselectfield'].widget.can_add_related, self.fields['myselectfield'].widget.can_change_related, self.fields['myselectfield'].widget.can_delete_related, )
Your Smartwatch Loves Tasker!
Your Smartwatch Loves Tasker!
Featured
Archive
- 2021
- 2020
- 2019
- 2018
- 2017
- Nov
- Oct
- Aug
- Jun
- Mar
- Feb
- 2016
- Oct
- Jun
- May
- Apr
- Mar
- Feb
- Jan
- 2015
- Nov
- Oct
- Aug
- Apr
- Mar
- Feb
- Jan
- 2014
- Sep
- Jul
- May
- Apr
- Mar
- Feb
- Jan
- 2013
- Nov
- Oct
- Sep
- Aug
- Jul
- Jun
- May
- Apr
- Mar
- Feb
- Jan
- 2012
- Dec
- Nov
- Oct
- Aug
- Jul
- Jun
- May
- Apr
- Jan
- 2011
- Dec
- Nov
- Oct
- Sep
- Aug
- Jul
- Jun
- May